package com.atwulidun.stack;

public class Calculator {
    public static void main(String[] args) {
        // 一、创建要计算的表达式
        String expression = "30*(661-30*(80-(60-40/20)))";
        // 二、创建数字栈和符号栈
        ArrayStack2 numStack = new ArrayStack2(10);// 创建一个数字栈
        ArrayStack2 operStack = new ArrayStack2(10);// 创建一个符号栈
        // 三、定义七个变量
        int index = 0;// 扫描表达式时的指针变量
        char ch = ' ';// 将每次扫描到的字符保存到ch中
        int num1;
        int num2;
        char oper;
        int res;
        String keepNum = "";// 若输入的数字为多位数时，需要对多位数的数字字符进行拼接保存到keepNum中
        // 四、利用while循环对计算表达式进行扫描
        while (true) {
            // 1、获取当前的字符
            ch = expression.substring(index, index + 1).charAt(0);// substring方法为左闭右开
            // 2、对当前字符进行判断，以决定入哪个栈，对'('和')'这两个特殊的字符要额外地进行判断
            if (operStack.isOper(ch)) {
                // 如果当前字符为运算符
                // '('的优先级是最低的，并且其入栈对之前栈内的元素完全没影响
                if (operStack.isEmpty() || ch == '(') {
                    operStack.push(ch);// 如果符号栈为空，则直接入栈
                } else if (ch == ')') {
                    // 如果是')'则不需要入栈，此时需要对与之相匹配的'('之间的部分进行计算，跟下面五、的计算方式一样
                    while (operStack.peek() != '(') {
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = (char) operStack.pop();
                        res = operStack.calculate(num1, num2, oper);
                        numStack.push(res);
                    }
                    // 此时还需要把'('pop出来，因为已经没有用了
                    operStack.pop();
                } else {
                    // 如果符号栈不为空，则要判断当前运算符和栈顶运算符优先级的大小
                    if (operStack.priority(ch) <= operStack.priority((char) operStack.peek())) {
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = (char) operStack.pop();
                        res = operStack.calculate(num1, num2, oper);
                        numStack.push(res);
                        operStack.push(ch);// char会自动向上转型为int
                    } else {
                        operStack.push(ch);
                    }
                }
            } else {
                // 如果当前字符为数字，且为一位数，则直接入数字栈
//                numStack.push(ch -48);// 注意：字符'1'对应的数值为49，则要将其-48才能得到int型的1
                // 如果输入的数字为多位数，则要对数字字符进行拼接
                keepNum += ch;
                // 如果ch刚好为运算表达式的最后一个字符，那么keepNum转换成int类型直接入数字栈即可，不然还要判断其下一位是不是运算符
                if (index == expression.length() - 1) {
                    numStack.push(Integer.parseInt(keepNum));
                    // 特别注意要把keepNum给清空！！！！！！！！！
                    keepNum = "";
                } else {
                    // 判断ch的下一位是不是运算符，是的话keepNum转换成int类型直接入数字栈
                    if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
                        numStack.push(Integer.parseInt(keepNum));
                        // 特别注意要把keepNum给清空！！！！！！！！！
                        keepNum = "";
                    }
                }
            }
            // 3、当前字符处理完，index指针要往后移动一位
            index++;
            // 4、设定跳出循环的判断条件
            if (index == expression.length()) {
                break;
            }
        }
        // 五、扫描完成后，就要进行计算了
        while (true) {
            // 当数字栈里面只剩一个元素或者符号栈为空时计算完成
            if (operStack.isEmpty()) {
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = (char) operStack.pop();
            res = operStack.calculate(num1, num2, oper);
            numStack.push(res);
        }
        // 六、计算完成，打印出最后的计算结果
        int finalRes = numStack.pop();
        System.out.println(expression + " = " + finalRes);
    }
}

class ArrayStack2 {
    private int maxSize;
    private int top = -1;
    private int[] stack;

    public ArrayStack2(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[maxSize];
    }

    public boolean isFull() {
        return top == (maxSize - 1);
    }

    public boolean isEmpty() {
        return top == -1;
    }

    public void push(int value) {
        if (isFull()) {
            System.out.println("栈满，不能再压栈！");
            return;// 因为该方法没有返回值，因此直接return即可
        }
        top++;
        stack[top] = value;
    }

    public int pop() {
        if (isEmpty()) {
            throw new RuntimeException("栈空，不能出栈！");// 因为该方法有返回值，返回null没有意义，因此抛出一个异常
        }
        int value = stack[top];
        top--;
        return value;
    }

    public void list() {
        if (isEmpty()) {
            System.out.println("栈为空！");
            return;
        }
        for (int i = top; i >= 0; i--) {
            System.out.printf("stack[%d] = %d\n", i, stack[i]);
        }
    }

    // 以下四个为相对于普通的ArrayStack新增加的方法，注意char和int之间的转化
    public int peek() {
        return stack[top];
    }

    // 得到运算符优先级的方法
    public int priority(char val) {
        if (val == '*' || val == '/') {
            return 1;
        } else if (val == '+' || val == '-') {
            return 0;
        } else {
            return -1;
        }
    }

    // 判断是否为运算符的方法
    public boolean isOper(char val) {
        return val == '+' || val == '-' || val == '*' || val == '/' || val == '(' || val == ')';
    }

    // 计算的方法
    public int calculate(int num1, int num2, char oper) {
        int res = 0;
        switch (oper) {
            case '+':
                res = num2 + num1;
                break;
            case '-':
                res = num2 - num1;// 注意要让后面的减去前面的
                break;
            case '*':
                res = num2 * num1;
                break;
            case '/':
                res = num2 / num1;// 注意要让后面的除以前面的
                break;
        }
        return res;
    }
}